<script>
import { GlDrawer, GlIcon, GlAlert, GlBadge, GlSkeletonLoader } from '@gitlab/ui';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import aiResponseSubscription from 'ee/graphql_shared/subscriptions/ai_completion_response.subscription.graphql';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_USER, TYPENAME_VULNERABILITY } from '~/graphql_shared/constants';
import { getMarkdown } from '~/rest_api';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import aiActionMutation from 'ee/graphql_shared/mutations/ai_action.mutation.graphql';
import { s__, __ } from '~/locale';
import SafeHtml from '~/vue_shared/directives/safe_html';
import ExplainVulnerabilityUserFeedback from './explain_vulnerability_user_feedback.vue';

export default {
  components: {
    GlDrawer,
    GlIcon,
    GlAlert,
    GlBadge,
    GlSkeletonLoader,
    ExplainVulnerabilityUserFeedback,
  },
  directives: { SafeHtml },
  props: {
    isOpen: { type: Boolean, required: true },
    vulnerability: { type: Object, required: true },
    includeSourceCode: { type: Boolean, required: true },
  },
  data() {
    return {
      isLoadingAiResponse: false,
      errorMessage: '',
      markdown: '',
    };
  },
  apollo: {
    $subscribe: {
      aiCompletionResponse: {
        query: aiResponseSubscription,
        skip: true, // We manually start and stop the subscription.
        variables() {
          return {
            resourceId: this.vulnerabilityGraphqlId,
            userId: convertToGraphQLId(TYPENAME_USER, gon.current_user_id),
          };
        },
        async result({ data }) {
          const { errors, responseBody } = data.aiCompletionResponse || {};
          // Once the subscription is ready, we will receive a null aiCompletionResponse. Once we get this, it's safe to
          // start the AI request mutation. Otherwise, it's possible that backend will send the AI response before the
          // subscription is ready, and the AI response will be lost.
          if (!data.aiCompletionResponse) {
            this.getExplanation();
          } else if (errors?.length) {
            this.showError(errors[0]);
          } else if (responseBody) {
            this.stopSubscription();
            // Use the markdown REST API to format the text using GitLab's format.
            const markdownResponse = await getMarkdown({ text: responseBody, gfm: true });
            this.isLoadingAiResponse = false;
            this.markdown = markdownResponse.data.html;
            // Wait for the browser to render the markdown first. $nextTick doesn't work here because it's too fast.
            requestAnimationFrame(() => {
              // Use renderGFM() to add syntax highlighting to the markdown.
              renderGFM(this.$refs.markdown);
            });
          }
        },
        error(e) {
          this.showError(e);
        },
      },
    },
  },
  computed: {
    getDrawerHeaderHeight() {
      return getContentWrapperHeight();
    },
    vulnerabilityGraphqlId() {
      return convertToGraphQLId(TYPENAME_VULNERABILITY, this.vulnerability.id);
    },
  },
  watch: {
    isOpen() {
      if (this.isOpen) {
        this.startSubscription();
      } else {
        this.stopSubscription();
      }
    },
  },
  methods: {
    getExplanation() {
      this.$apollo
        .mutate({
          mutation: aiActionMutation,
          variables: {
            input: {
              explainVulnerability: {
                resourceId: this.vulnerabilityGraphqlId,
                includeSourceCode: this.includeSourceCode,
              },
            },
          },
        })
        .then(({ data }) => {
          const error = data.aiAction.errors[0];

          if (error) {
            this.showError(error);
          }
        })
        .catch(this.showError);
    },
    startSubscription() {
      this.isLoadingAiResponse = true;
      this.errorMessage = '';
      this.markdown = '';
      this.$apollo.subscriptions.aiCompletionResponse.start();
    },
    stopSubscription() {
      this.$apollo.subscriptions.aiCompletionResponse.stop();
    },
    showError(error) {
      this.stopSubscription();
      this.isLoadingAiResponse = false;
      this.errorMessage = error;
    },
  },
  i18n: {
    drawerTitle: s__('Vulnerability|Explain this vulnerability'),
    drawerSubtitle: s__('Vulnerability|Response generated by AI'),
    maturityLabel: __('Beta'),
  },
  DRAWER_Z_INDEX,
};
</script>

<template>
  <gl-drawer
    :open="isOpen"
    :header-height="getDrawerHeaderHeight"
    header-sticky
    class="gl-reset-line-height"
    :z-index="$options.DRAWER_Z_INDEX"
    @close="$emit('close')"
  >
    <template #title>
      <div class="gl-display-flex gl-justify-content-start gl-align-items-center">
        <gl-icon name="tanuki-ai" class="gl-text-purple-600" />
        <span class="gl-font-size-markdown gl-font-weight-bold gl-mx-3">
          {{ s__('Vulnerability|Explain this vulnerability') }}
        </span>
        <gl-badge variant="neutral" size="sm">{{ $options.i18n.maturityLabel }}</gl-badge>
      </div>
    </template>

    <div class="gl-text-gray-500 gl-bg-gray-10 gl-border-none gl-text-center gl-p-4!">
      {{ s__('Vulnerability|Response generated by AI') }}
    </div>

    <div class="gl-pt-4!">
      <div
        v-if="isLoadingAiResponse"
        class="gl-border-1 gl-border-gray-100 gl-border-solid gl-p-4 gl-rounded-base gl-bg-gray-10"
      >
        <gl-skeleton-loader :lines="4" />
      </div>
      <div v-else-if="errorMessage">
        <gl-alert variant="danger" :dismissible="false">{{ errorMessage }}</gl-alert>
      </div>
      <div v-else ref="markdown" v-safe-html="markdown" data-testid="markdown" class="md"></div>
    </div>

    <template #footer>
      <explain-vulnerability-user-feedback v-if="markdown" :vulnerability="vulnerability" />
    </template>
  </gl-drawer>
</template>
